﻿
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Web;

using Swaf;
using Swaf.BizObj;
using Swaf.BizRule;
using Swaf.Container;

namespace PayPal
{
	public class PayPalBizRules : Syrinx.BaseBizRules
	{
		private static string s_endPointUrl = "https://api-3t.paypal.com/nvp";
		private static string s_sandboxEndPoint = "https://api-3t.sandbox.paypal.com/nvp";
		private static string s_defaultHost = "www.paypal.com";
		private static string s_sandboxHost = "www.sandbox.paypal.com";

		public IBizObj startPayPalExpressCheckout(IApplication app, IRuleContext context, IBizObj order)
		{
			IBizObj paypalInfo = app.bizObjMgr.create("PayPayInteraction");
			string host = s_defaultHost;
			string endPointUrl = s_endPointUrl;
			if (Cmn.IsTrue(ActiveCompany.get("settings.paypal.useSandbox")))
			{
				endPointUrl = s_sandboxEndPoint;
				host = s_sandboxHost;
			}

			Uri first = (Uri)app.sessionData.get("firstUrl");
			string hostName = first.Host;
			if (first.Port != 80)
				hostName += ":" + first.Port;
			if (!(app.sessionData["usingCompanyUrl"] is bool && (bool)app.sessionData["usingCompanyUrl"]))
				hostName += "/" + ActiveCompany.get("settings.sitePath");
			hostName = hostName.TrimEnd('/');
			string returnURL = string.Format("http://{0}/std/AfterExternalPayment.aspx", hostName);
			string cancelURL = string.Format("http://{0}/std/AfterExternalPayment.aspx?paypalCancel=true", hostName);

			NVPCodec encoder = new NVPCodec();
			encoder["METHOD"] = "SetExpressCheckout";
			encoder["RETURNURL"] = returnURL;
			encoder["CANCELURL"] = cancelURL;
			encoder["PAYMENTACTION"] = "Sale";
			encoder["CURRENCYCODE"] = "USD";
			encoder["ADDRESSOVERRIDE"] = "1";

			//Optional Shipping Address entered on the merchant site
			encoder["NAME"] = order.get("orderContact.fullName");
			encoder["SHIPTOSTREET"] = order.get("orderContact.defaultAddress.street1");
			encoder["SHIPTOSTREET2"] = order.get("orderContact.defaultAddress.street2");
			encoder["SHIPTOCITY"] = order.get("orderContact.defaultAddress.city");
			encoder["SHIPTOSTATE"] = order.get("orderContact.defaultAddress.stateProvince");
			encoder["SHIPTOZIP"] = order.get("orderContact.defaultAddress.zipCode");
			encoder["SHIPTOCOUNTRY"] = order.get("orderContact.defaultAddress.country.regionCode");

			decimal singleChargeSubTotal = 0.0M;
			int index = 0;
			int recurIndex = 0;
			foreach (IBizObj item in (IBizObjList)order.get("items"))
			{
				//For first version of paypal support, recurring payments for a product are specified in the part # of the product.
				//All products that support a recurring payment would be a seperate product and have a part # that ended in the
				//Format [[PriceType][NUM][Period]], such as [M4M] for price @ 4 months or [P12W] Price divided by frequency for 12 weeks.
				//If Period == 0 then subscription will last until canceled.  
				//The M price type is indicating a membership type product.  Membership products will not be lumped together into one
				//paypal billing agreement, so if someone orders 2 memberships, each one would have its own billing agreement which offers
				//the flexibility for one membership to be canceled seperately from others.
				//Valid Period names include:
				//D = Days, W = Weeks, S = SemiMonth, M = Month, Y = Year;
				//The frequency information should be managed in a different way, but that works is being saved for another round of
				//development.
				string partId = (string)item.get("calculatedProductPartId");
				Regex recurringRegex = new Regex(@"\[(?'priceType'[MP])(?'freq'\d+)(?'period'[DWSMY])\]", RegexOptions.IgnoreCase);
				Match m = recurringRegex.Match(partId);
				if (m.Success)
				{
					paypalInfo.put("hasRecurring", true);
					string freq = m.Groups["freq"].Value;
					string period = m.Groups["period"].Value;
					decimal price = (decimal)item.get("price");
					decimal paymentPrice = price;
					if (m.Groups["priceType"].Value.CompareTo("P") == 0)
						paymentPrice /= int.Parse(freq);
					string period1Name = null, period2Name = null, period3Name = null;
					int freq2 = int.Parse(freq) -1;
					string firstRecurringDate = null;
					switch (period)
					{
						case "D":
							period1Name = period2Name = "day";
							period3Name = "Day";
							firstRecurringDate = DateTime.UtcNow.AddDays(1).ToString("u");
							break;
						case "W":
							period1Name = period2Name = "week";
							period3Name = "Week";
							firstRecurringDate = DateTime.UtcNow.AddDays(7).ToString("u");
							break;
						case "S":
							period1Name = "semimonth";
							period2Name = "month";
							period3Name = "Semimonth";
							firstRecurringDate = DateTime.UtcNow.AddDays(14).ToString("u");
							freq2 = freq2 / 2;
							break;
						case "M":
							period1Name = period2Name = "month";
							period3Name = "Month";
							firstRecurringDate = DateTime.UtcNow.AddMonths(1).ToString("u");
							break;
						case "Y":
							period1Name = period2Name = "year";
							period3Name = "Year";
							firstRecurringDate = DateTime.UtcNow.AddYears(1).ToString("u");
							break;
					}

					singleChargeSubTotal += (paymentPrice * (int)item.get("quantity"));
					paypalInfo.put("hasFullPay", true);
					encoder["L_NAME" + index] = string.Format("First Payment of {0}", item.get("product.title"));
					encoder["L_AMT" + index] = paymentPrice.ToString("F");
					encoder["L_QTY" + index] = item.get("quantity");
					index++;

					for (int c = 0; c < (int)item.get("quantity"); c++)
					{
						IBizObj recurInfo = ((IBizObjList)paypalInfo.get("recurPayments")).insertNew();
						recurInfo.put("period", period3Name);
						recurInfo.put("frequency", int.Parse(freq));
						recurInfo.put("frequencyAmount", paymentPrice);
						recurInfo.put("firstRecurringDate", firstRecurringDate);
						recurInfo.put("orderItemId", item.get("id"));
						string recurDesc = (m.Groups["priceType"].Value.CompareTo("P") == 0) ?
							string.Format("{0} - {1} per {2} for {3} more {4}s", item.get("product.title"), paymentPrice.ToString("F"), period1Name, freq2, period2Name) :
							string.Format("{0} - {1} per {2}", item.get("product.title"), paymentPrice.ToString("F"), period1Name);

						encoder["L_BILLINGTYPE" + recurIndex] = "RecurringPayments";
						recurInfo.put("description", encoder["L_BILLINGAGREEMENTDESCRIPTION" + recurIndex] = recurDesc);
						recurIndex++;
					}
				}
				else
				{
					//Only non-recurring single payment products will get itemized in this way.  
					//Recurring items will not be listed here.
					singleChargeSubTotal += (decimal)item.get("total");
					paypalInfo.put("hasFullPay", true);
					encoder["L_NAME" + index] = item.get("product.title");
					encoder["L_AMT" + index] = ((decimal)item.get("price")).ToString("F");
					encoder["L_QTY" + index] = item.get("quantity");
					index++;
				}
			}

			decimal shippingAmount = (decimal)order.get("delivery.total");
			decimal totalTaxes = (decimal)order.get("totalTaxes");
			paypalInfo.put("totalBillNow", encoder["AMT"] = (singleChargeSubTotal + shippingAmount + totalTaxes).ToString("F"));
			encoder["SHIPPINGAMT"] = shippingAmount.ToString("F");
			encoder["ITEMAMT"] = (singleChargeSubTotal).ToString("F");
			encoder["TAXAMT"] = totalTaxes.ToString("F");


			string callParams = encoder.Encode();
			string callResp = HttpCall(callParams, endPointUrl);

			NVPCodec decoder = new NVPCodec();
			decoder.Decode(callResp);

			string strAck = decoder.get("ACK").ToLower();
			if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
			{
				paypalInfo.put("token", decoder["TOKEN"]);
				paypalInfo.put("firstNav", "https://" + host + "/cgi-bin/webscr?cmd=_express-checkout" + "&token=" + paypalInfo.get("token"));
			}
			else
			{
				FlexiMap m = new FlexiMap();
				m.put("code", decoder["L_ERRORCODE0"]);
				m.put("shortMessage", decoder["L_SHORTMESSAGE0"]);
				m.put("longMessage", decoder["L_LONGMESSAGE0"]);
				paypalInfo.put("error", m);
			}
			return paypalInfo;
		}

		public IBizObj completePayPalExpressCheckout(IApplication app, IRuleContext context, IBizObj order, IBizObj paypalInfo)
		{
			string host = s_defaultHost;
			string endPointUrl = s_endPointUrl;
			if (Cmn.IsTrue(ActiveCompany.get("settings.paypal.useSandbox")))
			{
				endPointUrl = s_sandboxEndPoint;
				host = s_sandboxHost;
			}

			NVPCodec encoder = new NVPCodec();
			encoder["METHOD"] = "GetExpressCheckoutDetails";
			encoder["TOKEN"] = paypalInfo.get("token");

			string callParams = encoder.Encode();
			string callResp = HttpCall(callParams, endPointUrl);

			NVPCodec decoder = new NVPCodec();
			decoder.Decode(callResp);

			string strAck = decoder.get("ACK").ToLower();
			if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
			{
				if (decoder["PAYERID"] != null)
				{
					order.put("orderContact.firstName", decoder["FIRSTNAME"]);
					order.put("orderContact.lastName", decoder["LASTNAME"]);
					order.put("orderContact.defaultAddress.street1", decoder["SHIPTOSTREET"]);
					order.put("orderContact.defaultAddress.street2", decoder["SHIPTOSTREET2"]);
					order.put("orderContact.defaultAddress.city", decoder["SHIPTOCITY"]);
					order.put("orderContact.defaultAddress.stateProvince", decoder["SHIPTOSTATE"]);
					order.put("orderContact.defaultAddress.zipCode", decoder["SHIPTOZIP"]);
					order.put("orderContact.defaultAddress.country.regionCode", decoder["SHIPTOCOUNTRYCODE"]);
					paypalInfo.put("payerId", decoder["PAYERID"]);
					paypalInfo.put("payerStatus", decoder["PAYERSTATUS"]);

					IBizObj paypalPaymentResults = app.bizObjMgr.create("PayPalPaymentInfo");

					if (Cmn.IsTrue(paypalInfo.get("hasFullPay")))
					{
						encoder = new NVPCodec();
						encoder["METHOD"] = "DoExpressCheckoutPayment";
						encoder["TOKEN"] = paypalInfo.get("token");
						encoder["PAYMENTACTION"] = "Sale";
						encoder["PAYERID"] = paypalInfo.get("payerId");
						encoder["AMT"] = paypalInfo.get("totalBillNow");

						callParams = encoder.Encode();
						callResp = HttpCall(callParams, endPointUrl);

						decoder = new NVPCodec();
						decoder.Decode(callResp);

						strAck = decoder.get("ACK").ToLower();
						if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
						{
							paypalPaymentResults.put("paymentStatus", decoder["PAYMENTSTATUS"]);
							paypalPaymentResults.put("paymentType", decoder["PAYMENTTYPE"]);
							paypalPaymentResults.put("orderTime", decoder["ORDERTIME"]);
							paypalPaymentResults.put("transactionId", decoder["TRANSACTIONID"]);
							paypalPaymentResults.put("transactionType", decoder["TRANSACTIONTYPE"]);
							paypalPaymentResults.put("pendingReason", decoder["PENDINGREASON"]);
						}
						else
						{
							paypalPaymentResults.put("error.code", decoder["L_ERRORCODE0"]);
							paypalPaymentResults.put("error.shortMessage", decoder["L_SHORTMESSAGE0"]);
							paypalPaymentResults.put("error.longMessage", decoder["L_LONGMESSAGE0"]);
						}
					}
					else
					{
						paypalPaymentResults.put("paymentType", "RecurringOnly");
						paypalPaymentResults.put("orderTime", DateTime.UtcNow);
					}
					if (Cmn.IsTrue(paypalInfo.get("hasRecurring")))
					{
						foreach (IBizObj recurInfo in (IBizObjList)paypalInfo.get("recurPayments"))
						{
							encoder = new NVPCodec();
							encoder["METHOD"] = "CreateRecurringPaymentsProfile";
							encoder["TOKEN"] = paypalInfo.get("token");

							int frequency = (int)recurInfo.get("frequency");
							int totalFreqs = frequency == 0 ? 0 : (frequency - 1);

							encoder["PROFILESTARTDATE"] = recurInfo.get("firstRecurringDate");
							encoder["DESC"] = recurInfo.get("description");
							encoder["BILLINGPERIOD"] = recurInfo.get("period");
							encoder["BILLINGFREQUENCY"] = "1";
							encoder["TOTALBILLINGCYCLES"] = totalFreqs;
							encoder["AMT"] = ((decimal)recurInfo.get("frequencyAmount")).ToString("F");
		
							callParams = encoder.Encode();
							callResp = HttpCall(callParams, endPointUrl);

							decoder = new NVPCodec();
							decoder.Decode(callResp);
							strAck = decoder.get("ACK").ToLower();
							if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
							{
								IBizObj recurStatusInfo = ((IBizObjList)paypalPaymentResults.get("recurPayments")).insertNew();
								recurStatusInfo.put("profileId", decoder["PROFILEID"]);
								recurStatusInfo.put("status", decoder["PROFILESTATUS"]);
								recurStatusInfo.put("correlationId", decoder["CORRELATIONID"]);
								recurStatusInfo.put("orderItemId", recurInfo.get("orderItemId"));
								recurStatusInfo.put("orderId", order.get("id"));
							}
						}
					}
					return paypalPaymentResults;
				}
				else
				{
					FlexiMap m = new FlexiMap();
					m.put("code", "GoodCancel");
					paypalInfo.put("error", m);
				}

			}
			else
			{
				FlexiMap m = new FlexiMap();
				m.put("code", decoder["L_ERRORCODE0"]);
				m.put("shortMessage", decoder["L_SHORTMESSAGE0"]);
				m.put("longMessage", decoder["L_LONGMESSAGE0"]);
				paypalInfo.put("error", m);
			}
			return paypalInfo;
		}


		public IBizObj doPayPalDirectPayment(IApplication app, IRuleContext context, IBizObj order, IBizObj paymentTerm)
		{
			string host = s_defaultHost;
			string endPointUrl = s_endPointUrl;
			if (Cmn.IsTrue(ActiveCompany.get("settings.paypal.useSandbox")))
			{
				endPointUrl = s_sandboxEndPoint;
				host = s_sandboxHost;
			}

			NVPCodec encoder = new NVPCodec();

			decimal singleChargeSubTotal = 0.0M;
			int index = 0;
			int recurIndex = 0;
			foreach (IBizObj item in (IBizObjList)order.get("items"))
			{
				//For first version of paypal support, recurring payments for a product are specified in the part # of the product.
				//All products that support a recurring payment would be a seperate product and have a part # that ended in the
				//Format [[PriceType][NUM][Period]], such as [M4M] for price @ 4 months or [P12W] Price divided by frequency for 12 weeks.
				//If Period == 0 then subscription will last until canceled.  
				//The M price type is indicating a membership type product.  Membership products will not be lumped together into one
				//paypal billing agreement, so if someone orders 2 memberships, each one would have its own billing agreement which offers
				//the flexibility for one membership to be canceled seperately from others.
				//Valid Period names include:
				//D = Days, W = Weeks, S = SemiMonth, M = Month, Y = Year;
				//The frequency information should be managed in a different way, but that works is being saved for another round of
				//development.
				string partId = (string)item.get("calculatedProductPartId");
				Regex recurringRegex = new Regex(@"\[(?'priceType'[MP])(?'freq'\d+)(?'period'[DWSMY])\]", RegexOptions.IgnoreCase);
				Match m = recurringRegex.Match(partId);
				if (m.Success)
				{
					string freq = m.Groups["freq"].Value;
					string period = m.Groups["period"].Value;
					decimal price = (decimal)item.get("price");
					decimal paymentPrice = price;
					if (m.Groups["priceType"].Value.CompareTo("P") == 0)
						paymentPrice /= int.Parse(freq);
					string period1Name = null, period2Name = null, period3Name = null;
					int freq2 = int.Parse(freq);
					string firstRecurringDate = null;
					switch (period)
					{
						case "D":
							period1Name = period2Name = "day";
							period3Name = "Day";
							firstRecurringDate = DateTime.UtcNow.AddDays(1).ToString("u");
							break;
						case "W":
							period1Name = period2Name = "week";
							period3Name = "Week";
							firstRecurringDate = DateTime.UtcNow.AddDays(7).ToString("u");
							break;
						case "S":
							period1Name = "semimonth";
							period2Name = "month";
							period3Name = "Semimonth";
							firstRecurringDate = DateTime.UtcNow.AddDays(14).ToString("u");
							freq2 = freq2 / 2;
							break;
						case "M":
							period1Name = period2Name = "month";
							period3Name = "Month";
							firstRecurringDate = DateTime.UtcNow.AddMonths(1).ToString("u");
							break;
						case "Y":
							period1Name = period2Name = "year";
							period3Name = "Year";
							firstRecurringDate = DateTime.UtcNow.AddYears(1).ToString("u");
							break;
					}

					singleChargeSubTotal += (paymentPrice * (int)item.get("quantity"));
					encoder["L_NAME" + index] = string.Format("First Payment of {0}", item.get("product.title"));
					encoder["L_AMT" + index] = paymentPrice.ToString("F");
					encoder["L_QTY" + index] = item.get("quantity");
					index++;

					for (int c = 0; c < (int)item.get("quantity"); c++)
					{
						NVPCodec recurEnc = new NVPCodec();
						recurEnc["METHOD"] = "CreateRecurringPaymentsProfile";

						int frequency = int.Parse(freq);
						int totalFreqs = frequency == 0 ? 0 : (frequency - 1);
						string recurDesc = (m.Groups["priceType"].Value.CompareTo("P") == 0) ?
							string.Format("{0} - {1} per {2} for {3} {4}s", item.get("product.title"), paymentPrice.ToString("F"), period1Name, freq2, period2Name) :
							string.Format("{0} - {1} per {2}", item.get("product.title"), paymentPrice.ToString("F"), period1Name);

						recurEnc["SUBSCRIBERNAME"] = order.get("orderContact.fullName");
						recurEnc["PROFILESTARTDATE"] = firstRecurringDate;
						recurEnc["DESC"] = recurDesc;
						recurEnc["BILLINGPERIOD"] = period3Name;
						recurEnc["BILLINGFREQUENCY"] = "1";
						recurEnc["TOTALBILLINGCYCLES"] = totalFreqs;
						recurEnc["AMT"] = paymentPrice.ToString("F");
						recurEnc["L_BILLINGTYPE0"] = "RecurringPayments";

						setupCCInfo(recurEnc, order, paymentTerm);

						string recurParams = recurEnc.Encode();
						string recurResp = HttpCall(recurParams, endPointUrl);

						NVPCodec recurDecoder = new NVPCodec();
						recurDecoder.Decode(recurResp);
						string recurAck = recurDecoder.get("ACK").ToLower();
						if (recurAck != null && (recurAck == "success" || recurAck == "successwithwarning"))
						{
							IBizObj recurStatusInfo = ((IBizObjList)paymentTerm.get("recurPayments")).insertNew();
							recurStatusInfo.put("profileId", recurDecoder["PROFILEID"]);
							recurStatusInfo.put("status", recurDecoder["PROFILESTATUS"]);
							recurStatusInfo.put("correlationId", recurDecoder["CORRELATIONID"]);
							recurStatusInfo.put("orderItemId", item.get("id"));
							recurStatusInfo.put("orderId", order.get("id"));
						}
					}
				}
				else
				{
					//Only non-recurring single payment products will get itemized in this way.  
					//Recurring items will not be listed here.
					singleChargeSubTotal += (decimal)item.get("total");
					encoder["L_NAME" + index] = item.get("product.title");
					encoder["L_AMT" + index] = ((decimal)item.get("price")).ToString("F");
					encoder["L_QTY" + index] = item.get("quantity");
					index++;
				}
			}

			if (index > 0)
			{
				encoder["METHOD"] = "DoDirectPayment";
				encoder["PAYMENTACTION"] = "Sale";
				encoder["CURRENCYCODE"] = "USD";
				//Optional Shipping Address entered on the merchant site
				encoder["SHIPTONAME"] = order.get("orderContact.fullName");
				encoder["SHIPTOSTREET"] = order.get("orderContact.defaultAddress.street1");
				if(!Cmn.IsEmpty(order.get("orderContact.defaultAddress.street2")))
					encoder["SHIPTOSTREET2"] = order.get("orderContact.defaultAddress.street2");
				encoder["SHIPTOCITY"] = order.get("orderContact.defaultAddress.city");
				encoder["SHIPTOSTATE"] = order.get("orderContact.defaultAddress.stateProvince");
				encoder["SHIPTOZIP"] = order.get("orderContact.defaultAddress.zipCode");
				encoder["SHIPTOCOUNTRY"] = order.get("orderContact.defaultAddress.country.regionCode");

				decimal shippingAmount = (decimal)order.get("delivery.total");
				decimal totalTaxes = (decimal)order.get("totalTaxes");
				encoder["AMT"] = (singleChargeSubTotal + shippingAmount + totalTaxes).ToString("F");
				encoder["SHIPPINGAMT"] = shippingAmount.ToString("F");
				encoder["ITEMAMT"] = (singleChargeSubTotal).ToString("F");
				encoder["TAXAMT"] = totalTaxes.ToString("F");

				setupCCInfo(encoder, order, paymentTerm);


				string callParams = encoder.Encode();
				string callResp = HttpCall(callParams, endPointUrl);

				NVPCodec decoder = new NVPCodec();
				decoder.Decode(callResp);

				string strAck = decoder.get("ACK").ToLower();
				if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
				{
				}
				else
				{
					FlexiMap m = new FlexiMap();
					m.put("code", decoder["L_ERRORCODE0"]);
					m.put("shortMessage", decoder["L_SHORTMESSAGE0"]);
					m.put("longMessage", decoder["L_LONGMESSAGE0"]);
					paymentTerm.put("error", m);
				}
			}

			return paymentTerm;
		}


		protected void setupCCInfo(NVPCodec encoder, IBizObj order, IBizObj paymentInfo)
		{
			encoder["CREDITCARDTYPE"] = paymentInfo.get("cardType");
			encoder["ACCT"] = paymentInfo.get("cardNumber");
			encoder["EXPDATE"] = paymentInfo.get("expireDateValidateFormat");
			encoder["CVV2"] = paymentInfo.get("securityCode");
			encoder["FIRSTNAME"] = order.get("orderContact.firstName");
			encoder["LASTNAME"] = order.get("orderContact.lastName");
			encoder["STREET"] = order.get("orderContact.defaultAddress.street1");
			if (!Cmn.IsEmpty(order.get("orderContact.defaultAddress.street2")))
				encoder["STREET2"] = order.get("orderContact.defaultAddress.street2");
			encoder["CITY"] = order.get("orderContact.defaultAddress.city");
			encoder["STATE"] = order.get("orderContact.defaultAddress.stateProvince");
			encoder["COUNTRYCODE"] = order.get("orderContact.defaultAddress.country.regionCode");
			encoder["ZIP"] = order.get("orderContact.defaultAddress.zipCode");
		}


		/// <summary>
		/// HttpCall: The main method that is used for all API calls
		/// </summary>
		/// <param name="NvpRequest"></param>
		/// <returns></returns>
		protected string HttpCall(string request, string endPointUrl) //CallNvpServer
		{
			//To Add the credentials from the profile
			string strPost = request + "&" + buildCredentialsNVPString();

			HttpWebRequest objRequest = (HttpWebRequest)WebRequest.Create(endPointUrl);
			objRequest.Timeout = 10000;
			objRequest.Method = "POST";
			objRequest.ContentLength = strPost.Length;

			try
			{
				using (StreamWriter myWriter = new StreamWriter(objRequest.GetRequestStream()))
				{
					myWriter.Write(strPost);
				}
			}
			catch (Exception e)
			{
				/*
				if (log.IsFatalEnabled)
				{
					log.Fatal(e.Message, this);
				}*/
			}

			//Retrieve the Response returned from the NVP API call to PayPal
			HttpWebResponse objResponse = (HttpWebResponse)objRequest.GetResponse();
			string result;
			using (StreamReader sr = new StreamReader(objResponse.GetResponseStream()))
			{
				result = sr.ReadToEnd();
			}

			//Logging the response of the transaction
			/* if (log.IsInfoEnabled)
			 {
				 log.Info("Result :" +
						   " Elapsed Time : " + (DateTime.Now - startDate).Milliseconds + " ms" +
						  result);
			 }
			 */
			return result;
		}

		protected string buildCredentialsNVPString()
		{
			NVPCodec codec = new NVPCodec();

			codec["USER"] = ActiveCompany.get("settings.paypal.userName");
			codec["PWD"] = ActiveCompany.get("settings.paypal.password");
			codec["SIGNATURE"] = ActiveCompany.get("settings.paypal.signature");
			codec["SUBJECT"] = ""; //Is this really needed?  Its not mentioned at all in the online documents, but example code uses it...
			codec["VERSION"] = "54.0"; //"2.3";

			return codec.Encode();
		}
	}


	public class NVPCodec
	{
		protected static Regex s_cardNum = new Regex(@"ACCT=[^&]+&");
		protected Dictionary<string, object> m_values = new Dictionary<string, object>();

		/// <summary>
		/// Returns the built NVP string of all name/value pairs in the Hashtable
		/// </summary>
		/// <returns></returns>
		public string Encode()
		{
			StringBuilder sb = new StringBuilder();
			foreach(KeyValuePair<string,object> item in m_values)
			{
				string name = UrlEncode(item.Key);
				string value = UrlEncode(item.Value.ToString());
				sb.Append("&").Append(name).Append('=').Append(value);
			}
			string retVal = sb.Length>0?sb.ToString(1, sb.Length-1):"";
			Application.currentApp.Log.println(Swaf.Logging.LoggingLevels.Medium, "PayPal-Out: {0}", s_cardNum.Replace(retVal, "ACCT=xxxxxxxx"));
			return retVal;
		}

		/// <summary>
		/// Decoding the string
		/// </summary>
		/// <param name="nvpstring"></param>
		public void Decode(string nvpstring)
		{
			Application.currentApp.Log.println(Swaf.Logging.LoggingLevels.Medium, "PayPal-In: {0}", nvpstring);
			m_values.Clear();
			foreach (string nvp in nvpstring.Split('&'))
			{
				string[] tokens = nvp.Split('=');
				if (tokens.Length >= 2)
				{
					string name = UrlDecode(tokens[0]);
					string value = UrlDecode(tokens[1]);
					m_values[name] = value;
				}
			}
		}
		public object this[object key]
		{
			get
			{
				object v = null;
				m_values.TryGetValue(key.ToString(), out v);
				return v;
			}
			set
			{
				m_values[key.ToString()] = value;
			}
		}

		public void add(string key, object val)
		{
			m_values[key] = val;
		}
		public string get(string key)
		{
			object val = null;
			m_values.TryGetValue(key, out val);
			return val==null?"":val.ToString();
		}

		public static string UrlDecode(string s) { return HttpUtility.UrlDecode(s); }
		public static string UrlEncode(string s) { return HttpUtility.UrlEncode(s); }

	}
}
